<template>
  <!-- 虚拟列表：固定子项高度 -->
  <div id="app">
    <!-- 可视区(container) -->
    <div :style="{ height: VIEW_HEIGHT + 'px' }" ref="containerRef" class="container" @scroll="scrollEvent">
      <!-- 内容虚拟撑开区(pillar) -->
      <div class="pillar" :style="{ height: state.pillarHeight + 'px' }"></div>
      <!-- 内容可见区(content) -->
      <div class="content" :style="{ transform }">
        <div
          v-for="item in visibleData"
          ref="items"
          :key="item.id"
          class="content-item"
          :style="{ height: height + 'px', lineHeight: height + 'px' }"
        >
          {{ item.id }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, computed, onMounted } from "vue";
const VIEW_HEIGHT = 300;
const BUFFER = 2;
const { list, height } = defineProps<{
  list: any[];
  height: number;
}>();

const state = reactive({
  startIndex: 0,
  endIndex: 0,
  pillarHeight: 0,
  viewCount: 0,
  offsetTop: 0,
});

const transform = computed(() => `translate(0, ${state.offsetTop}px)`);

const visibleData = computed(() => {
  return list.slice(state.startIndex, state.endIndex);
});

function scrollEvent(e: Event) {
  const scrollTop = (e.target as HTMLElement).scrollTop;
  state.startIndex = Math.floor(scrollTop / height);
  state.endIndex = state.startIndex + state.viewCount + BUFFER;

  state.offsetTop = state.startIndex * height;
}

onMounted(() => {
  state.pillarHeight = height * list.length;
  state.viewCount = Math.ceil(VIEW_HEIGHT / height);

  state.endIndex = state.viewCount + BUFFER;
});
</script>

<style>
.container {
  width: 200px;
  -webkit-overflow-scrolling: touch;
  overflow: auto;
  position: relative;
  background-color: aqua;
}
.content {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  background-color: yellow;
}
.content-item {
  box-sizing: border-box;
  border: 1px solid #ddd;
  text-align: center;
  color: #333;
}
</style>
